package eu.catengine;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Window;
import android.view.WindowManager;
import eu.catengine.common.handler.TouchHandler;
import eu.catengine.common.iface.IDisposable;
import eu.catengine.common.manager.AnimationManager;
import eu.catengine.common.manager.AudioManager;
import eu.catengine.common.manager.DataManager;
import eu.catengine.common.manager.FontManager;
import eu.catengine.common.manager.SceneManager;
import eu.catengine.common.manager.ScreenManager;
import eu.catengine.common.manager.TextureManager;
import eu.catengine.primitive.Point;

/**
 * Podstawowa klasa silnika. <br>
 * Służy do organizacji pracy poszczególnych elementów. <br>
 * Chcąc uruchomić silnik należy dziedziczyc domyślne activity po tej klasie. <br>
 * <br>
 * Po dziedziczeniu nie należy zapominać o stworzeniu domyślnej sceny i dodaniu
 * jej do SceneManager'a.
 * 
 * @author mbienkowski
 */
public abstract class CatEngineCore extends Activity implements Renderer, IDisposable
{
    // GL view
    private GLSurfaceView mView;

    // Handlers
    private TouchHandler mTouchHandler = new TouchHandler();

    // Managers
    private ScreenManager mScreenManager = ScreenManager.getInstance();
    private SceneManager mSceneManager = SceneManager.getInstance();
    private TextureManager mTextureManager = TextureManager.getInstance();
    private AnimationManager mAnimationManager = AnimationManager.getInstance();
    private AudioManager mAudioManager = AudioManager.getInstance();
    private FontManager mFontManager = FontManager.getInstance();
    private DataManager mDataManager = DataManager.getInstance();

    // Other settings
    private static int mFPS = 50;
    private static long mFrameDuration = 1000 / 50;

    private int mSceenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;

    @SuppressLint("WorldReadableFiles")
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
	Log.d("CatEngine", "onCreate");

	DataManager.getInstance().setSharedPreferences(getSharedPreferences("Preference", MODE_WORLD_READABLE));

	this.loadSettings();

	// Set fullscreen window
	this.requestWindowFeature(Window.FEATURE_NO_TITLE);
	this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
	this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

	// Set GL surface
	this.mView = new GLSurfaceView(this);
	this.mView.setRenderer(this);
	this.setContentView(mView);

	// Set screen mode
	this.setRequestedOrientation(mSceenOrientation);

	super.onCreate(savedInstanceState);
    }

    @Override
    protected void onStart()
    {
	Log.d("CatEngine", "onStart");
	this.loadSettings();
	super.onStart();
    }

    @Override
    protected void onPause()
    {
	Log.d("CatEngine", "onPause");
	this.onEnginePause();
	super.onPause();
    }

    @Override
    protected void onResume()
    {
	Log.d("CatEngine", "onResume");
	this.onEngineResume();
	super.onResume();
    }

    @Override
    protected void onDestroy()
    {
	Log.d("CatEngine", "onDestroy");

	dispose();

	super.onDestroy();
    }

    /**
     * Metoda wywoływana w momencie akcji na ekranie dotykowym.
     */
    public boolean onTouchEvent(MotionEvent event)
    {
	Point eventPoint = new Point(event.getX(), event.getY());

	if (!mScreenManager.isClickableArea(eventPoint))
	{
	    return super.onTouchEvent(event);
	}

	mTouchHandler.TouchAction(event);

	return super.onTouchEvent(event);
    }

    /**
     * Metoda wywoływana w momencie wciśnięcia fizycznego przycisku na
     * urządzeniu.
     */
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event)
    {
	if (mSceneManager.getActiveScene().onButtonDown(keyCode, event))
	{
	    return true;
	}

	return super.onKeyDown(keyCode, event);
    }

    /*
     * Renderer methods
     */

    /**
     * Metoda wywoływana do renderowania klatki środkowiska OpenGL.
     */
    @Override
    public void onDrawFrame(GL10 gl)
    {
	mSceneManager.getActiveScene().updateRunningScene();
	mSceneManager.getActiveScene().drawScene(gl);
    }

    /**
     * Metoda wywoływana w momencie zmiany parametrów wyświetlania powierzchni
     * do renderowania.
     */
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height)
    {
	mScreenManager.updateViewportSize(width, height);
	gl.glViewport(mScreenManager.getOffsetX(), mScreenManager.getOffsetY(), mScreenManager.getViewportWidth(), mScreenManager.getViewportHeight());

	gl.glMatrixMode(GL10.GL_PROJECTION);
	gl.glLoadIdentity();

	gl.glOrthof(0, mScreenManager.getScreenWidth(), 0, mScreenManager.getScreenHeight(), -1.0f, 1.0f);
	gl.glMatrixMode(GL10.GL_MODELVIEW);
    }

    /**
     * Metoda wywoływana w momencie stworzenia powierzchni do renderowania.
     */
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config)
    {
	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	gl.glClearDepthf(1.0f);

	gl.glEnable(GL10.GL_TEXTURE_2D);
	gl.glEnable(GL10.GL_DEPTH_TEST);
	gl.glEnable(GL10.GL_BLEND);
	gl.glEnable(GL10.GL_DITHER);

	gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
	gl.glDepthFunc(GL10.GL_LEQUAL);
	gl.glShadeModel(GL10.GL_SMOOTH);
	gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

	gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
	gl.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST);

	gl.glDisable(GL10.GL_COLOR_MATERIAL);
	gl.glDisable(GL10.GL_TEXTURE_2D);

	this.mFontManager.setGL(gl);
	this.mTextureManager.setGL(gl);

	this.mFontManager.setAssetManager(getAssets());
	this.mAudioManager.setAssetManager(getAssets());
	this.mTextureManager.setAssetManager(getAssets());

	this.loadResources();
	this.createScenes();
    }

    /**
     * Zwraca czas trwania pojedynczej klatki.
     */
    public static long getFrameDuration()
    {
	return mFrameDuration;
    }

    /**
     * Ustawia pożądaną wartość klatek na sekundę.
     */
    public static void setFPS(int FPS)
    {
	mFPS = FPS;
	mFrameDuration = 1000 / FPS;
    }

    /**
     * Zwraca wartość określoną jako pożądana wartość klatek na sekundę.
     */
    public static int getFPS()
    {
	return mFPS;
    }

    /**
     * Zwraca obiekt managera ekranu.
     */
    public ScreenManager getScreenManager()
    {
	return mScreenManager;
    }

    /**
     * Zwraca obiekt managera sceny.
     */
    public SceneManager getSceneManager()
    {
	return mSceneManager;
    }

    /**
     * Zwraca obiekt managera tekstur.
     */
    public TextureManager getTextureManager()
    {
	return mTextureManager;
    }

    /**
     * Zwraca obiekt managera animacji.
     */
    public AnimationManager getAnimationManager()
    {
	return mAnimationManager;
    }

    /**
     * Zwraca obiekt managera dzwieków.
     */
    public AudioManager getAudioManager()
    {
	return mAudioManager;
    }

    /**
     * Zwraca obiekt managera czcionek.
     */
    public FontManager getFontManager()
    {
	return mFontManager;
    }

    /**
     * Zwraca obiekt managera danych.
     */
    public DataManager getDataManager()
    {
	return mDataManager;
    }

    /**
     * Ustanawia pożądaną orientację ekranu.
     * 
     * @param screenOrientation
     *            <code>int</code> Wartość określająca orientację ekranu.<br>
     *            Przykład <code>
     *            ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE</code>.
     */
    public void setScreenOrientation(int screenOrientation)
    {
	this.mSceenOrientation = screenOrientation;
    }

    @Override
    public void dispose()
    {
	mTouchHandler.dispose();

	ScreenManager.getInstance().dispose();
	SceneManager.getInstance().dispose();
	TextureManager.getInstance().dispose();
	AnimationManager.getInstance().dispose();
	AudioManager.getInstance().dispose();
	FontManager.getInstance().dispose();
    }

    /**
     * Służy do zmiany/wczytania podstawowych ustawien elementow.<br>
     * Np. zmiana orientacji ekranu, porzadanej ilosci klatek na sekunde, ...
     */
    protected abstract void loadSettings();

    /**
     * Służy do wczytania wszystkich zewnetrznych elementow takich jak tekstury,
     * dzwieki, animacje.
     */
    protected abstract void loadResources();

    /**
     * Służy do utworzenia obiektow scen i dodanie ich do managera sceny.
     */
    protected abstract void createScenes();

    /**
     * Metoda wywoływana w przypadku, gdy silnik przechodzi w stan pauzy.
     */
    protected abstract void onEnginePause();

    /**
     * Metoda wywoływana w przypadku, gdy silnik przechodzi w stan aktywny z
     * pauzy.
     */
    protected abstract void onEngineResume();

}
